'use strict';

const { time } = require('lib/time-utils.js');
const { Logger } = require('lib/logger.js');
const Resource = require('lib/models/Resource.js');
const { dirname } = require('lib/path-utils.js');
const { FsDriverNode } = require('./fs-driver-node.js');
const lodash = require('lodash');
const exec = require('child_process').exec;
const fs = require('fs-extra');

const baseDir = `${dirname(__dirname)}/tests/fuzzing`;
const syncDir = `${baseDir}/sync`;
const joplinAppPath = `${__dirname}/main.js`;
let syncDurations = [];

const fsDriver = new FsDriverNode();
Logger.fsDriver_ = fsDriver;
Resource.fsDriver_ = fsDriver;

const logger = new Logger();
logger.addTarget('console');
logger.setLevel(Logger.LEVEL_DEBUG);

process.on('unhandledRejection', (reason, p) => {
	console.error('Unhandled promise rejection', p, 'reason:', reason);
});

function createClient(id) {
	return {
		id: id,
		profileDir: `${baseDir}/client${id}`,
	};
}

async function createClients() {
	let output = [];
	let promises = [];
	for (let clientId = 0; clientId < 2; clientId++) {
		let client = createClient(clientId);
		promises.push(fs.remove(client.profileDir));
		promises.push(
			execCommand(client, 'config sync.target 2').then(() => {
				return execCommand(client, `config sync.2.path ${syncDir}`);
			})
		);
		output.push(client);
	}

	await Promise.all(promises);

	return output;
}

function randomElement(array) {
	if (!array.length) return null;
	return array[Math.floor(Math.random() * array.length)];
}

function randomWord() {
	const words = [
		'belief',
		'scandalous',
		'flawless',
		'wrestle',
		'sort',
		'moldy',
		'carve',
		'incompetent',
		'cruel',
		'awful',
		'fang',
		'holistic',
		'makeshift',
		'synonymous',
		'questionable',
		'soft',
		'drop',
		'boot',
		'whimsical',
		'stir',
		'idea',
		'adhesive',
		'present',
		'hilarious',
		'unusual',
		'divergent',
		'probable',
		'depend',
		'suck',
		'belong',
		'advise',
		'straight',
		'encouraging',
		'wing',
		'clam',
		'serve',
		'fill',
		'nostalgic',
		'dysfunctional',
		'aggressive',
		'floor',
		'baby',
		'grease',
		'sisters',
		'print',
		'switch',
		'control',
		'victorious',
		'cracker',
		'dream',
		'wistful',
		'adaptable',
		'reminiscent',
		'inquisitive',
		'pushy',
		'unaccountable',
		'receive',
		'guttural',
		'two',
		'protect',
		'skin',
		'unbiased',
		'plastic',
		'loutish',
		'zip',
		'used',
		'divide',
		'communicate',
		'dear',
		'muddled',
		'dinosaurs',
		'grip',
		'trees',
		'well-off',
		'calendar',
		'chickens',
		'irate',
		'deranged',
		'trip',
		'stream',
		'white',
		'poison',
		'attack',
		'obtain',
		'theory',
		'laborer',
		'omniscient',
		'brake',
		'maniacal',
		'curvy',
		'smoke',
		'babies',
		'punch',
		'hammer',
		'toothbrush',
		'same',
		'crown',
		'jagged',
		'peep',
		'difficult',
		'reject',
		'merciful',
		'useless',
		'doctor',
		'mix',
		'wicked',
		'plant',
		'quickest',
		'roll',
		'suffer',
		'curly',
		'brother',
		'frighten',
		'cold',
		'tremendous',
		'move',
		'knot',
		'lame',
		'imaginary',
		'capricious',
		'raspy',
		'aunt',
		'loving',
		'wink',
		'wooden',
		'hop',
		'free',
		'drab',
		'fire',
		'instrument',
		'border',
		'frame',
		'silent',
		'glue',
		'decorate',
		'distance',
		'powerful',
		'pig',
		'admit',
		'fix',
		'pour',
		'flesh',
		'profuse',
		'skinny',
		'learn',
		'filthy',
		'dress',
		'bloody',
		'produce',
		'innocent',
		'meaty',
		'pray',
		'slimy',
		'sun',
		'kindhearted',
		'dime',
		'exclusive',
		'boast',
		'neat',
		'ruthless',
		'recess',
		'grieving',
		'daily',
		'hateful',
		'ignorant',
		'fence',
		'spring',
		'slim',
		'education',
		'overflow',
		'plastic',
		'gaping',
		'chew',
		'detect',
		'right',
		'lunch',
		'gainful',
		'argue',
		'cloistered',
		'horses',
		'orange',
		'shame',
		'bitter',
		'able',
		'sail',
		'magical',
		'exist',
		'force',
		'wheel',
		'best',
		'suit',
		'spurious',
		'partner',
		'request',
		'dog',
		'gusty',
		'money',
		'gaze',
		'lonely',
		'company',
		'pale',
		'tempt',
		'rat',
		'flame',
		'wobble',
		'superficial',
		'stop',
		'protective',
		'stare',
		'tongue',
		'heal',
		'railway',
		'idiotic',
		'roll',
		'puffy',
		'turn',
		'meeting',
		'new',
		'frightening',
		'sophisticated',
		'poke',
		'elderly',
		'room',
		'stimulating',
		'increase',
		'moor',
		'secret',
		'lean',
		'occur',
		'country',
		'damp',
		'evanescent',
		'alluring',
		'oafish',
		'join',
		'thundering',
		'cars',
		'awesome',
		'advice',
		'unruly',
		'ray',
		'wind',
		'anxious',
		'fly',
		'hammer',
		'adventurous',
		'shop',
		'cook',
		'trucks',
		'nonchalant',
		'addition',
		'base',
		'abashed',
		'excuse',
		'giants',
		'dramatic',
		'piquant',
		'coach',
		'possess',
		'poor',
		'finger',
		'wide-eyed',
		'aquatic',
		'welcome',
		'instruct',
		'expert',
		'evasive',
		'hug',
		'cute',
		'return',
		'mice',
		'damage',
		'turkey',
		'quiet',
		'bewildered',
		'tidy',
		'pointless',
		'outrageous',
		'medical',
		'foolish',
		'curve',
		'grandiose',
		'gullible',
		'hapless',
		'gleaming',
		'third',
		'grin',
		'pipe',
		'egg',
		'act',
		'physical',
		'eager',
		'side',
		'milk',
		'tearful',
		'fertile',
		'average',
		'glamorous',
		'strange',
		'yak',
		'terrific',
		'thin',
		'near',
		'snails',
		'flowery',
		'authority',
		'fish',
		'curious',
		'perpetual',
		'healthy',
		'health',
		'match',
		'fade',
		'chemical',
		'economic',
		'drawer',
		'avoid',
		'lying',
		'minister',
		'lick',
		'powder',
		'decay',
		'desire',
		'furry',
		'faint',
		'beam',
		'sordid',
		'fax',
		'tail',
		'bawdy',
		'cherry',
		'letter',
		'clover',
		'ladybug',
		'teeth',
		'behavior',
		'black',
		'amazing',
		'pink',
		'waste',
		'island',
		'forgetful',
		'needless',
		'lock',
		'waves',
		'boundary',
		'receipt',
		'handy',
		'religion',
		'hypnotic',
		'aftermath',
		'explain',
		'sense',
		'mundane',
		'rambunctious',
		'second',
		'preserve',
		'alarm',
		'dusty',
		'event',
		'blow',
		'weigh',
		'value',
		'glorious',
		'jail',
		'sigh',
		'cemetery',
		'serious',
		'yummy',
		'cattle',
		'understood',
		'limit',
		'alert',
		'fear',
		'lucky',
		'tested',
		'surround',
		'dolls',
		'pleasant',
		'disillusioned',
		'discover',
		'tray',
		'night',
		'seemly',
		'liquid',
		'worry',
		'pen',
		'bent',
		'gruesome',
		'war',
		'teeny-tiny',
		'common',
		'judge',
		'symptomatic',
		'bed',
		'trot',
		'unequaled',
		'flowers',
		'friends',
		'damaged',
		'peel',
		'skip',
		'show',
		'twist',
		'worthless',
		'brush',
		'look',
		'behave',
		'imperfect',
		'week',
		'petite',
		'direction',
		'soda',
		'lively',
		'coal',
		'coil',
		'release',
		'berserk',
		'books',
		'impossible',
		'replace',
		'cough',
		'chunky',
		'torpid',
		'discreet',
		'material',
		'bomb',
		'soothe',
		'crack',
		'hope',
		'license',
		'frightened',
		'breathe',
		'maddening',
		'calculator',
		'committee',
		'paltry',
		'green',
		'subsequent',
		'arrest',
		'gigantic',
		'tasty',
		'metal',
		'willing',
		'man',
		'stem',
		'nonstop',
		'route',
		'impulse',
		'government',
		'comfortable',
		'include',
		'literate',
		'multiply',
		'test',
		'vast',
		'exercise',
		'addicted',
		'agreeable',
		'lace',
		'toes',
		'young',
		'water',
		'end',
		'wash',
		'glossy',
		'round',
		'staking',
		'sink',
		'open',
		'spot',
		'trip',
		'fierce',
		'robust',
		'pastoral',
		'drown',
		'dress',
		'machine',
		'calculating',
		'holiday',
		'crabby',
		'disgusting',
		'plan',
		'sleet',
		'sleepy',
		'typical',
		'borrow',
		'possible',
		'curtain',
		'airplane',
		'industry',
		'nut',
		'rough',
		'wacky',
		'rock',
		'enormous',
		'uninterested',
		'sugar',
		'rake',
		'consist',
		'wrist',
		'basket',
		'chop',
		'wet',
		'street',
		'known',
		'settle',
		'bless',
		'cluttered',
		'wild',
		'expand',
		'angle',
		'snake',
		'yawn',
		'hate',
		'flood',
		'rabid',
		'spiteful',
		'anger',
		'market',
		'bizarre',
		'force',
		'majestic',
		'scissors',
		'beg',
		'rifle',
		'foregoing',
		'cactus',
		'funny',
		'eggnog',
		'wish',
		'high-pitched',
		'drop',
		'camp',
		'scarf',
		'car',
		'groan',
		'wonderful',
		'wealthy',
		'cup',
		'lock',
		'available',
		'previous',
		'jam',
		'political',
		'vacation',
		'three',
		'desk',
		'fry',
		'aspiring',
		'productive',
		'clear',
		'bored',
		'flashy',
		'plug',
		'precede',
		'abhorrent',
		'muddle',
		'flimsy',
		'paste',
		'need',
		'reward',
		'frail',
		'obnoxious',
		'creature',
		'whip',
		'unbecoming',
		'lake',
		'unused',
		'chin',
		'tour',
		'zephyr',
		'experience',
		'building',
		'scrub',
		'correct',
		'hover',
		'panicky',
		'scorch',
		'diligent',
		'hulking',
		'ubiquitous',
		'tedious',
		'aberrant',
		'file',
		'accidental',
		'mist',
		'blue-eyed',
		'trite',
		'nondescript',
		'cows',
		'wait',
		'test',
		'snotty',
		'amuck',
		'jump',
		'lackadaisical',
		'grey',
		'tawdry',
		'strong',
		'land',
		'kind',
		'star',
		'ludicrous',
		'stupid',
		'telling',
		'use',
		'bruise',
		'whirl',
		'cream',
		'harsh',
		'aboriginal',
		'substantial',
		'brawny',
		'tease',
		'pollution',
		'weather',
		'degree',
		'dry',
		'film',
		'obey',
		'closed',
		'dependent',
		'want',
		'undesirable',
		'stamp',
		'relax',
		'foot',
		'obscene',
		'successful',
		'wriggle',
		'drain',
		'greasy',
		'escape',
		'cross',
		'odd',
		'boring',
		'absorbed',
		'houses',
		'suppose',
		'suit',
		'moon',
		'ceaseless',
		'explode',
		'clap',
		'pop',
		'courageous',
		'miss',
		'notebook',
		'delirious',
		'form',
		'pretty',
		'sock',
		'grotesque',
		'noxious',
		'record',
		'stop',
		'saw',
		'thing',
		'dislike',
		'cloth',
		'six',
		'jar',
		'unnatural',
		'spiffy',
		'itchy',
		'secretary',
		'move',
		'certain',
		'unkempt',
		'sassy',
		'queue',
		'shrug',
		'crow',
		'heavenly',
		'desert',
		'screw',
		'vessel',
		'mug',
		'encourage',
		'icy',
		'enthusiastic',
		'throat',
		'whistle',
		'ignore',
		'miniature',
		'squeak',
		'scarecrow',
		'fluttering',
		'hang',
		'icicle',
		'lie',
		'juicy',
		'empty',
		'baseball',
		'various',
		'promise',
		'abortive',
		'descriptive',
		'high',
		'spy',
		'faded',
		'talk',
		'air',
		'messup',
		'decorous',
		'sneaky',
		'mark',
		'sack',
		'ultra',
		'chivalrous',
		'lethal',
		'expect',
		'disgusted',
		'reaction',
		'fireman',
		'private',
		'ritzy',
		'manage',
		'actor',
		'rely',
		'uppity',
		'thread',
		'bat',
		'space',
		'underwear',
		'blood',
		'nine',
		'maid',
		'shelf',
		'hanging',
		'shop',
		'prick',
		'wound',
		'sloppy',
		'offer',
		'increase',
		'clear',
		'slap',
		'rude',
		'poised',
		'wretched',
		'cause',
		'quince',
		'tame',
		'remarkable',
		'abject',
		'sail',
		'guide',
		'subdued',
		'spiky',
		'debonair',
		'chicken',
		'tired',
		'hum',
		'land',
		'scared',
		'splendid',
		'guess',
		'cast',
		'rub',
		'magnificent',
		'ants',
		'overwrought',
		'interfere',
		'gorgeous',
		'office',
		'trade',
		'sniff',
		'melted',
		'bore',
		'point',
		'pet',
		'purple',
		'brake',
		'flavor',
		'toe',
		'prickly',
		'zinc',
		'homely',
		'modern',
		'kindly',
		'whisper',
		'bare',
		'annoyed',
		'glass',
		'noisy',
		'null',
		'thoughtless',
		'skirt',
		'dock',
		'rings',
		'mind',
		'neck',
		'macho',
		'wave',
		'history',
		'play',
		'road',
		'profit',
		'word',
		'opposite',
		'dreary',
		'governor',
		'horse',
		'trust',
		'elbow',
		'kiss',
		'crayon',
		'stitch',
		'excited',
		'needy',
		'arrange',
		'easy',
		'alcoholic',
		'safe',
		'lumpy',
		'monkey',
		'smile',
		'capable',
		'untidy',
		'extra-small',
		'memory',
		'selective',
		'reproduce',
		'old-fashioned',
		'overrated',
		'texture',
		'knit',
		'downtown',
		'risk',
		'pot',
		'sofa',
		'righteous',
		'wren',
		'pull',
		'carry',
		'aboard',
		'listen',
		'classy',
		'thank',
		'shocking',
		'condition',
		'root',
		'attempt',
		'swim',
		'frog',
		'hurt',
		'army',
		'title',
		'handsomely',
		'town',
		'guiltless',
		'thaw',
		'spell',
		'selfish',
		'disturbed',
		'tramp',
		'girls',
		'utopian',
		'noiseless',
		'trail',
		'bashful',
		'business',
		'rhetorical',
		'snail',
		'sign',
		'plausible',
		'left',
		'design',
		'tall',
		'violent',
		'wasteful',
		'beautiful',
		'breezy',
		'tap',
		'murder',
		'talented',
		'needle',
		'creator',
		'imagine',
		'flippant',
		'dead',
		'bone',
		'coherent',
		'relation',
		'aromatic',
		'mountainous',
		'face',
		'ask',
		'picture',
		'pedal',
		'colour',
		'obese',
		'group',
		'top',
		'bubble',
		'pinch',
		'optimal',
		'school',
		'bathe',
		'flagrant',
		'check',
		'deliver',
		'pass',
		'tan',
		'crate',
		'hose',
		'debt',
		'faulty',
		'longing',
		'hollow',
		'invincible',
		'afford',
		'lovely',
		'ticket',
		'changeable',
		'subtract',
		'fumbling',
		'responsible',
		'confused',
		'woman',
		'touch',
		'watch',
		'zesty',
		'library',
		'jail',
		'wrap',
		'terrify',
		'brick',
		'popcorn',
		'cooperative',
		'peck',
		'pocket',
		'property',
		'buzz',
		'tiresome',
		'digestion',
		'exciting',
		'nation',
		'juvenile',
		'shade',
		'copper',
		'wanting',
		'deer',
		'waste',
		'man',
		'join',
		'spotty',
		'amused',
		'mountain',
		'waggish',
		'bushes',
		'tense',
		'river',
		'heartbreaking',
		'help',
		'mine',
		'narrow',
		'smash',
		'scrawny',
		'tame',
		'rain',
		'playground',
		'airport',
		'astonishing',
		'level',
		'befitting',
		'animal',
		'heat',
		'painful',
		'cellar',
		'ski',
		'sedate',
		'knowing',
		'vigorous',
		'change',
		'eight',
		'ship',
		'work',
		'strip',
		'robin',
		'tank',
		'challenge',
		'vacuous',
		'representative',
		'regret',
		'tightfisted',
		'erratic',
		'club',
		'imported',
		'therapeutic',
		'rainstorm',
		'luxuriant',
		'relieved',
		'day',
		'system',
		'apologise',
		'male',
		'prepare',
		'malicious',
		'naive',
		'whistle',
		'curl',
		'hobbies',
		'trousers',
		'stereotyped',
		'dad',
		'endurable',
		'grass',
		'hot',
		'bomb',
		'morning',
		'guide',
		'keen',
		'plot',
		'accept',
		'disastrous',
		'macabre',
		'year',
		'spicy',
		'absorbing',
		'sticks',
		'efficient',
		'drain',
		'warm',
		'rice',
		'utter',
		'fact',
		'marked',
		'ratty',
		'chalk',
		'towering',
		'treat',
		'nest',
		'annoy',
		'jealous',
		'stamp',
		'effect',
		'cautious',
		'jelly',
		'feigned',
		'gabby',
		'corn',
		'volleyball',
		'pan',
		'psychedelic',
		'fairies',
		'silent',
		'zonked',
		'bump',
		'trouble',
		'mass',
		'queen',
		'things',
		'bury',
		'sister',
		'quiet',
		'colossal',
		'puncture',
		'four',
		'attend',
		'love',
		'wiry',
		'vegetable',
		'destruction',
		'note',
		'pies',
		'resolute',
		'load',
		'fancy',
		'tacky',
		'periodic',
		'abandoned',
		'vivacious',
		'blush',
		'wrathful',
		'miscreant',
		'call',
		'striped',
		'wiggly',
		'supreme',
		'hand',
		'impolite',
		'rule',
		'deserted',
		'concern',
		'cover',
		'harbor',
		'waiting',
		'soggy',
		'psychotic',
		'ancient',
		'sponge',
		'domineering',
		'elegant',
		'impartial',
		'unlock',
		'abrasive',
		'count',
		'flight',
		'neighborly',
		'roof',
		'bulb',
		'auspicious',
		'automatic',
		'magic',
		'sign',
		'amusing',
		'orange',
		'branch',
		'sulky',
		'attack',
		'fetch',
		'number',
		'jellyfish',
		'start',
		'alike',
		'touch',
		'sour',
		'wary',
		'minor',
		'punish',
		'connect',
		'protest',
		'pie',
		'kaput',
		'doubtful',
		'friendly',
		'simplistic',
		'smart',
		'vanish',
		'applaud',
		'jumbled',
		'ready',
		'yell',
		'support',
		'squash',
		'raise',
		'parallel',
		'super',
		'jazzy',
		'crush',
		'apathetic',
		'water',
		'food',
		'thrill',
		'permit',
		'heady',
		'last',
		'mine',
		'signal',
		'smoke',
		'preach',
		'x-ray',
		'name',
		'birth',
		'minute',
		'steel',
		'bedroom',
		'female',
		'acrid',
		'riddle',
		'attractive',
		'earth',
		'crack',
		'muscle',
		'alive',
		'guarded',
		'sweater',
		'donkey',
		'doubt',
		'lettuce',
		'magenta',
		'live',
		'farm',
		'glib',
		'bow',
		'fascinated',
		'friend',
		'practise',
		'remember',
		'bleach',
		'hungry',
		'voiceless',
		'pin',
		'sparkling',
		'report',
		'arm',
		'sad',
		'shaggy',
		'parcel',
		'wail',
		'flash',
		'territory',
		'functional',
		'wise',
		'screeching',
		'appliance',
		'future',
		'appear',
		'team',
		'rabbit',
		'porter',
		'paint',
		'flat',
		'amusement',
		'ocean',
		'head',
		'geese',
		'wash',
		'embarrassed',
		'tub',
		'boundless',
		'freezing',
		'mushy',
		'surprise',
		'temporary',
		'marble',
		'recondite',
		'telephone',
		'zipper',
		'pine',
		'reign',
		'pump',
		'tangy',
		'reply',
		'toys',
		'purpose',
		'songs',
		'form',
		'delicious',
		'wood',
		'horn',
		'nutty',
		'fruit',
		'lumber',
		'potato',
		'cheat',
		'cloudy',
		'visit',
		'reduce',
		'destroy',
		'deafening',
		'full',
		'warlike',
		'mitten',
		'cover',
		'earthy',
		'seashore',
		'yarn',
		'tenuous',
		'pause',
		'boil',
		'dogs',
		'tough',
		'knife',
		'shy',
		'naughty',
		'existence',
		'fire',
		'eminent',
		'remove',
		'juice',
		'sleep',
		'voyage',
		'balance',
		'unsightly',
		'plough',
		'ill-fated',
		'pumped',
		'motionless',
		'allow',
		'trade',
		'warm',
		'toad',
		'wave',
		'wall',
		'pigs',
		'circle',
		'rejoice',
		'ear',
		'drink',
		'found',
		'taboo',
		'object',
		'old',
		'temper',
		'plant',
		'public',
		'picayune',
		'blot',
		'delight',
		'carpenter',
		'dispensable',
		'tire',
		'cow',
		'furniture',
		'rightful',
		'mute',
		'gentle',
		'gifted',
		'ragged',
		'stiff',
		'retire',
		'compare',
		'sable',
		'hole',
		'judicious',
		'chilly',
		'sparkle',
		'futuristic',
		'love',
		'bubble',
		'travel',
		'name',
		'numberless',
		'succeed',
		'acoustic',
		'lowly',
		'society',
		'injure',
		'agree',
		'reason',
		'party',
		'wool',
		'careful',
		'hook',
		'bell',
		'ball',
		'attach',
		'scream',
		'development',
		'happy',
		'appreciate',
		'disagree',
		'request',
		'march',
		'rampant',
		'scrape',
		'sack',
		'hair',
		'measure',
		'owe',
		'grubby',
		'vein',
		'boy',
		'punishment',
		'smoggy',
		'wry',
		'immense',
		'shoe',
		'pack',
		'brash',
		'cave',
		'sincere',
		'adorable',
		'fantastic',
		'attraction',
		'racial',
		'jittery',
		'defiant',
		'honey',
		'paper',
		'weight',
		'bee',
		'blind',
		'birthday',
		'toothsome',
		'trick',
		'guard',
		'fog',
		'handle',
		'dirty',
		'salt',
		'rinse',
		'nippy',
		'observe',
		'suggestion',
		'weak',
		'instinctive',
		'frequent',
		'detail',
		'verse',
		'quirky',
		'scattered',
		'toy',
		'aware',
		'distribution',
		'repulsive',
		'draconian',
		'bucket',
		'harm',
		'radiate',
		'bang',
		'shrill',
		'living',
		'rhythm',
		'obsequious',
		'drum',
		'inject',
		'skate',
		'beds',
		'smash',
		'order',
		'stitch',
		'ground',
		'nosy',
		'kick',
		'dusty',
		'home',
		'rot',
		'frame',
		'jam',
		'sky',
		'soap',
		'rescue',
		'energetic',
		'grape',
		'massive',
		'deeply',
		'dazzling',
		'park',
		'pull',
		'number',
		'abundant',
		'barbarous',
		'drag',
		'ajar',
		'close',
		'moan',
		'haircut',
		'shade',
		'married',
		'cats',
		'thirsty',
		'dirt',
		'vagabond',
		'fearful',
		'squealing',
		'squalid',
		'zebra',
		'murky',
		'sheet',
		'fat',
		'follow',
		'bikes',
		'unpack',
		'materialistic',
		'surprise',
		'arch',
		'selection',
		'acoustics',
		'helpless',
		'thoughtful',
		'cry',
		'quarrelsome',
		'arrogant',
		'illegal',
		'sudden',
		'elite',
		'tomatoes',
		'spoil',
		'flower',
		'shivering',
		'front',
		'caption',
		'volcano',
		'ugliest',
		'ambitious',
		'pickle',
		'interrupt',
		'nervous',
		'approve',
		'messy',
		'dust',
		'oceanic',
		'brass',
		'tremble',
		'fine',
		'nerve',
		'lunchroom',
		'hard',
		'engine',
		'erect',
		'flower',
		'cynical',
		'irritating',
		'tight',
		'cobweb',
		'gray',
		'invention',
		'snatch',
		'account',
		'sharp',
		'spooky',
		'squeamish',
		'eatable',
		'share',
		'need',
		'moaning',
		'suspect',
		'rush',
		'rural',
		'false',
		'float',
		'bite',
		'careless',
		'sidewalk',
		'cowardly',
		'stroke',
		'educated',
		'ugly',
		'type',
		'wandering',
		'bolt',
		'mint',
		'fit',
		'large',
		'extra-large',
		'defeated',
		'kitty',
		'tacit',
		'abiding',
		'grandfather',
		'trains',
		'lamp',
		'habitual',
		'fast',
		'offbeat',
		'accurate',
		'many',
		'fortunate',
		'lyrical',
		'charge',
		'illustrious',
		'transport',
		'wakeful',
		'cable',
		'ordinary',
		'string',
		'question',
		'train',
		'fancy',
		'kick',
		'enchanting',
		'jobless',
		'ahead',
		'comparison',
		'loose',
		'dance',
		'add',
		'wonder',
		'stale',
		'earn',
		'reflective',
		'bright',
		'true',
		'statuesque',
		'amount',
		'matter',
		'repair',
		'care',
		'ruin',
		'terrible',
		'elastic',
		'spiders',
		'craven',
		'lamentable',
		'decision',
		'swing',
		'connection',
		'gaudy',
		'knowledge',
		'cheap',
		'lazy',
		'step',
		'dinner',
		'rod',
		'agreement',
		'comb',
		'mean',
		'past',
		'knotty',
		'busy',
		'quicksand',
		'match',
		'early',
		'long',
		'onerous',
		'ambiguous',
		'worried',
		'spade',
		'happen',
		'crook',
		'dapper',
		'grate',
		'announce',
		'plate',
		'haunt',
		'friction',
		'actually',
		'chance',
		'example',
		'rapid',
		'zealous',
		'necessary',
		'ink',
		'mere',
		'shock',
		'huge',
		'jaded',
		'spill',
		'store',
		'fuzzy',
		'table',
		'bottle',
		'halting',
		'spark',
		'end',
		'remain',
		'transport',
		'seat',
		'leg',
		'long-term',
		'clip',
		'grumpy',
		'shake',
		'walk',
		'try',
		'action',
		'soup',
		'short',
		'hurry',
		'square',
		'belligerent',
		'thankful',
		'beginner',
		'small',
		'bumpy',
		'silly',
		'badge',
		'marvelous',
		'wealth',
		'open',
		'unequal',
		'scatter',
		'pest',
		'fool',
		'step',
		'groovy',
		'childlike',
		'door',
		'bouncy',
		'believe',
		'incredible',
		'box',
		'unhealthy',
		'swanky',
		'abrupt',
		'depressed',
		'flaky',
		'famous',
		'detailed',
		'regret',
		'envious',
		'natural',
		'apparel',
		'spare',
		'mark',
		'ten',
		'power',
		'glistening',
		'arrive',
		'animated',
		'slip',
		'heap',
		'shaky',
		'unfasten',
		'contain',
		'inexpensive',
		'introduce',
		'shallow',
		'rule',
		'gather',
		'pump',
		'humorous',
		'acceptable',
		'womanly',
		'giddy',
		'silk',
		'yoke',
		'straw',
		'invite',
		'one',
		'red',
		'growth',
		'unadvised',
		'measly',
		'flap',
		'puzzled',
		'regular',
		'painstaking',
		'little',
		'plain',
		'tumble',
		'rest',
		'fabulous',
		'melt',
		'label',
		'truculent',
		'internal',
		'passenger',
		'zippy',
		'bright',
		'earsplitting',
		'tooth',
		'veil',
		'grip',
		'square',
		'stuff',
		'gate',
		'level',
		'stone',
		'observation',
		'time',
		'workable',
		'bird',
		'realise',
		'spotted',
		'coast',
		'quiver',
		'rebel',
		'entertain',
		'rotten',
		'loss',
		'collect',
		'meal',
		'satisfy',
		'military',
		'bake',
		'cagey',
		'redundant',
		'treatment',
		'knock',
		'blink',
		'scale',
		'board',
		'fair',
		'nebulous',
		'sip',
		'homeless',
		'place',
		'complain',
		'joke',
		'bat',
		'winter',
		'choke',
		'frantic',
		'chubby',
		'highfalutin',
		'troubled',
		'whole',
		'rose',
		'delightful',
		'loaf',
		'afraid',
		'sturdy',
		'class',
		'cultured',
		'yielding',
		'broken',
		'kittens',
		'absurd',
		'discovery',
		'next',
		'disarm',
		'dangerous',
		'lively',
		'reflect',
		'chief',
		'teeny',
		'pencil',
		'ban',
		'grade',
		'size',
		'dashing',
		'thought',
		'breath',
		'empty',
		'hellish',
		'shock',
		'sea',
		'weary',
		'payment',
		'limping',
		'premium',
		'grateful',
		'somber',
		'tax',
		'coach',
		'vulgar',
		'stretch',
		'tow',
		'branch',
		'insurance',
		'yam',
		'stormy',
		'wish',
		'snow',
		'cute',
		'milky',
		'battle',
		'far',
		'roasted',
		'slip',
		'adamant',
		'awake',
		'employ',
		'tangible',
		'tickle',
		'jog',
		'hysterical',
		'meddle',
		'parsimonious',
		'judge',
		'educate',
		'respect',
		'sound',
		'oven',
		'gratis',
		'station',
		'train',
		'purring',
		'steady',
		'carriage',
		'humdrum',
		'voracious',
		'jolly',
		'brainy',
		'proud',
		'elfin',
		'cure',
		'motion',
		'record',
		'quizzical',
		'pail',
		'bike',
		'faithful',
		'approval',
		'vague',
		'fall',
		'store',
		'normal',
		'rock',
		'bear',
		'bounce',
		'giant',
		'satisfying',
		'crooked',
		'lopsided',
		'vest',
		'separate',
		'sneeze',
		'teaching',
		'general',
		'meat',
		'festive',
		'historical',
		'line',
		'north',
		'tip',
		'son',
		'damaging',
		'nimble',
		'broad',
		'list',
		'confuse',
		'first',
		'deserve',
		'steep',
		'last',
		'rich',
		'oval',
		'thick',
		'glow',
		'great',
		'clammy',
		'cheer',
		'untidy',
		'scientific',
		'noise',
		'stomach',
		'undress',
		'big',
		'bite-sized',
		'enter',
		'cake',
		'aloof',
		'abnormal',
		'month',
		'grab',
		'well-groomed',
		'silver',
		'art',
		'berry',
		'giraffe',
		'complete',
		'billowy',
		'thumb',
		'squeal',
		'crib',
		'discussion',
		'even',
		'stretch',
		'mellow',
		'angry',
		'grouchy',
		'absent',
		'snow',
		'middle',
		'stingy',
		'mourn',
		'deep',
		'honorable',
		'nice',
		'verdant',
		'reach',
		'lavish',
		'sin',
		'interest',
		'whine',
		'tug',
		'vengeful',
		'rhyme',
		'stay',
		'upset',
		'hesitant',
		'tent',
		'wire',
		'gold',
		'momentous',
		'yellow',
		'cap',
		'delicate',
		'youthful',
		'twig',
		'burly',
		'devilish',
		'chess',
		'wide',
		'misty',
		'useful',
		'memorise',
		'madly',
		'plants',
		'spectacular',
		'accessible',
		'collar',
		'truck',
		'harmony',
		'uncovered',
		'beef',
		'low',
		'channel',
		'abusive',
		'analyse',
		'observant',
		'snobbish',
		'duck',
		'excellent',
		'intend',
		'wreck',
		'testy',
		'care',
		'shoes',
		'charming',
		'demonic',
		'can',
		'wipe',
		'acidic',
		'watch',
		'decisive',
		'brave',
		'greet',
		'imminent',
		'influence',
		'oranges',
		'seal',
		'eggs',
		'knowledgeable',
		'ashamed',
		'shiny',
		'inconclusive',
		'remind',
		'house',
		'solid',
		'quixotic',
		'describe',
		'support',
	];
	return randomElement(words);
}

function execCommand(client, command, options = {}) {
	let exePath = `node ${joplinAppPath}`;
	let cmd = `${exePath} --update-geolocation-disabled --env dev --log-level debug --profile ${client.profileDir} ${command}`;
	logger.info(`${client.id}: ${command}`);

	if (options.killAfter) {
		logger.info(`Kill after: ${options.killAfter}`);
	}

	return new Promise((resolve, reject) => {
		let childProcess = exec(cmd, (error, stdout, stderr) => {
			if (error) {
				if (error.signal == 'SIGTERM') {
					resolve('Process was killed');
				} else {
					logger.error(stderr);
					reject(error);
				}
			} else {
				resolve(stdout.trim());
			}
		});

		if (options.killAfter) {
			setTimeout(() => {
				logger.info('Sending kill signal...');
				childProcess.kill();
			}, options.killAfter);
		}
	});
}

async function clientItems(client) {
	let itemsJson = await execCommand(client, 'dump');
	try {
		return JSON.parse(itemsJson);
	} catch (error) {
		throw new Error(`Cannot parse JSON: ${itemsJson}`);
	}
}

function randomTag(items) {
	let tags = [];
	for (let i = 0; i < items.length; i++) {
		if (items[i].type_ != 5) continue;
		tags.push(items[i]);
	}

	return randomElement(tags);
}

function randomNote(items) {
	let notes = [];
	for (let i = 0; i < items.length; i++) {
		if (items[i].type_ != 1) continue;
		notes.push(items[i]);
	}

	return randomElement(notes);
}

async function execRandomCommand(client) {
	let possibleCommands = [
		['mkbook {word}', 40], // CREATE FOLDER
		['mknote {word}', 70], // CREATE NOTE
		[
			async () => {
				// DELETE RANDOM ITEM
				let items = await clientItems(client);
				let item = randomElement(items);
				if (!item) return;

				if (item.type_ == 1) {
					return execCommand(client, `rm -f ${item.id}`);
				} else if (item.type_ == 2) {
					return execCommand(client, `rm -r -f ${item.id}`);
				} else if (item.type_ == 5) {
					// tag
				} else {
					throw new Error(`Unknown type: ${item.type_}`);
				}
			},
			30,
		],
		[
			async () => {
				// SYNC
				let avgSyncDuration = averageSyncDuration();
				let options = {};
				if (!isNaN(avgSyncDuration)) {
					if (Math.random() >= 0.5) {
						options.killAfter = avgSyncDuration * Math.random();
					}
				}
				return execCommand(client, 'sync --random-failures', options);
			},
			30,
		],
		[
			async () => {
				// UPDATE RANDOM ITEM
				let items = await clientItems(client);
				let item = randomNote(items);
				if (!item) return;

				return execCommand(client, `set ${item.id} title "${randomWord()}"`);
			},
			50,
		],
		[
			async () => {
				// ADD TAG
				let items = await clientItems(client);
				let note = randomNote(items);
				if (!note) return;

				let tag = randomTag(items);
				let tagTitle = !tag || Math.random() >= 0.9 ? `tag-${randomWord()}` : tag.title;

				return execCommand(client, `tag add ${tagTitle} ${note.id}`);
			},
			50,
		],
	];

	let cmd = null;
	while (true) {
		cmd = randomElement(possibleCommands);
		let r = 1 + Math.floor(Math.random() * 100);
		if (r <= cmd[1]) break;
	}

	cmd = cmd[0];

	if (typeof cmd === 'function') {
		return cmd();
	} else {
		cmd = cmd.replace('{word}', randomWord());
		return execCommand(client, cmd);
	}
}

function averageSyncDuration() {
	return lodash.mean(syncDurations);
}

function randomNextCheckTime() {
	let output = time.unixMs() + 1000 + Math.random() * 1000 * 120;
	logger.info(`Next sync check: ${time.unixMsToIso(output)} (${Math.round((output - time.unixMs()) / 1000)} sec.)`);
	return output;
}

function findItem(items, itemId) {
	for (let i = 0; i < items.length; i++) {
		if (items[i].id == itemId) return items[i];
	}
	return null;
}

function compareItems(item1, item2) {
	let output = [];
	for (let n in item1) {
		if (!item1.hasOwnProperty(n)) continue;
		let p1 = item1[n];
		let p2 = item2[n];

		if (n == 'notes_') {
			p1.sort();
			p2.sort();
			if (JSON.stringify(p1) !== JSON.stringify(p2)) {
				output.push(n);
			}
		} else {
			if (p1 !== p2) output.push(n);
		}
	}
	return output;
}

function findMissingItems_(items1, items2) {
	let output = [];

	for (let i = 0; i < items1.length; i++) {
		let item1 = items1[i];
		let found = false;
		for (let j = 0; j < items2.length; j++) {
			let item2 = items2[j];
			if (item1.id == item2.id) {
				found = true;
				break;
			}
		}

		if (!found) {
			output.push(item1);
		}
	}

	return output;
}

function findMissingItems(items1, items2) {
	return [findMissingItems_(items1, items2), findMissingItems_(items2, items1)];
}

async function compareClientItems(clientItems) {
	let itemCounts = [];
	for (let i = 0; i < clientItems.length; i++) {
		let items = clientItems[i];
		itemCounts.push(items.length);
	}
	logger.info(`Item count: ${itemCounts.join(', ')}`);

	let missingItems = findMissingItems(clientItems[0], clientItems[1]);
	if (missingItems[0].length || missingItems[1].length) {
		logger.error('Items are different');
		logger.error(missingItems);
		process.exit(1);
	}

	let differences = [];
	let items = clientItems[0];
	for (let i = 0; i < items.length; i++) {
		let item1 = items[i];
		for (let clientId = 1; clientId < clientItems.length; clientId++) {
			let item2 = findItem(clientItems[clientId], item1.id);
			if (!item2) {
				logger.error(`Item not found on client ${clientId}:`);
				logger.error(item1);
				process.exit(1);
			}

			let diff = compareItems(item1, item2);
			if (diff.length) {
				differences.push({
					item1: JSON.stringify(item1),
					item2: JSON.stringify(item2),
				});
			}
		}
	}

	if (differences.length) {
		logger.error('Found differences between items:');
		logger.error(differences);
		process.exit(1);
	}
}

async function main() {
	await fs.remove(syncDir);

	let clients = await createClients();
	let clientId = 0;

	for (let i = 0; i < clients.length; i++) {
		clients[i].activeCommandCount = 0;
	}

	function handleCommand(clientId) {
		if (clients[clientId].activeCommandCount >= 1) return;

		clients[clientId].activeCommandCount++;

		execRandomCommand(clients[clientId])
			.catch(error => {
				logger.info(`Client ${clientId}:`);
				logger.error(error);
			})
			.then(r => {
				if (r) {
					logger.info(`Client ${clientId}:\n${r.trim()}`);
				}
				clients[clientId].activeCommandCount--;
			});
	}

	let nextSyncCheckTime = randomNextCheckTime();
	let state = 'commands';

	setInterval(async () => {
		if (state == 'waitForSyncCheck') return;

		if (state == 'syncCheck') {
			state = 'waitForSyncCheck';
			let clientItems = [];
			// Up to 3 sync operations must be performed by each clients in order for them
			// to be perfectly in sync - in order for each items to send their changes
			// and get those from the other clients, and to also get changes that are
			// made as a result of a sync operation (eg. renaming a folder that conflicts
			// with another one).
			for (let loopCount = 0; loopCount < 3; loopCount++) {
				for (let i = 0; i < clients.length; i++) {
					let beforeTime = time.unixMs();
					await execCommand(clients[i], 'sync');
					syncDurations.push(time.unixMs() - beforeTime);
					if (syncDurations.length > 20) syncDurations.splice(0, 1);
					if (loopCount === 2) {
						let dump = await execCommand(clients[i], 'dump');
						clientItems[i] = JSON.parse(dump);
					}
				}
			}

			await compareClientItems(clientItems);

			nextSyncCheckTime = randomNextCheckTime();
			// eslint-disable-next-line require-atomic-updates
			state = 'commands';
			return;
		}

		if (state == 'waitForClients') {
			for (let i = 0; i < clients.length; i++) {
				if (clients[i].activeCommandCount > 0) return;
			}

			state = 'syncCheck';
			return;
		}

		if (state == 'commands') {
			if (nextSyncCheckTime <= time.unixMs()) {
				state = 'waitForClients';
				return;
			}

			handleCommand(clientId);
			clientId++;
			if (clientId >= clients.length) clientId = 0;
		}
	}, 100);
}

main(process.argv).catch(error => {
	logger.error(error);
});
